package dao

import (
	gojson "encoding/json"
	"github.com/go-redis/redis"
	"github.com/tianjigames/fairy/pojo"
	"github.com/tianjigames/fairy/util"
	"github.com/topfreegames/pitaya/component"
	"github.com/topfreegames/pitaya/logger"
	"github.com/xormplus/xorm"
	"reflect"
	"time"
)

var (
	expirationTime time.Duration
	DeleteRoleKeppTime time.Duration
)

type BaseDao struct {
	component.Base
	enginer *xorm.Engine
	redisClient *redis.Client
}

func (p *BaseDao) Init()  {
	enginer,err := util.ManagerUtil.GetDbEnginer()
	if err != nil {
		logger.Log.Errorf("BaseDao Init failed,error:%s",err.Error())
		return
	}

	p.enginer = enginer

	rdClient,err := util.ManagerUtil.GetRedisClient()
	if err != nil {
		logger.Log.Errorf("BaseDao Init failed,error:%s",err.Error())
		return
	}
	p.redisClient = rdClient
}

func (p *BaseDao) Add(data pojo.DbData) error {
	err := p.AddDb(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] Add failed,error:%s",err.Error())
		return err
	}

	if p.UseCache(data){
		err = p.AddCache(data)
		if err != nil {
			logger.Log.Errorf("[BaseDao] Add failed,error:%s",err.Error())
			return err
		}
	}

	return nil
}

func (p *BaseDao) AddDb(data pojo.DbData) error {
	_,err := p.enginer.Insert(data)
	if err != nil {
		logger.Log.Errorf("AddDb failed,error:%s",err.Error())
		return err
	}
	return nil
}

func (b *BaseDao) AddCache(data pojo.DbData) error {
	data.SetStatus(pojo.OptInsert)
	bytes,err := gojson.Marshal(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] AddCache failed,error:%s",err.Error())
		return err
	}

	err = b.redisClient.HSet(data.TableName(),data.GetDataKey(),bytes).Err()
	if err != nil {
		logger.Log.Errorf("[BaseDao] AddCache failed,error:%s",err.Error())
		return err
	}

	return nil
}

func (p *BaseDao) Update(data pojo.DbData,query string,args...interface{}) error {
	_,err := p.UpdateDb(data,query,args...)
	if err != nil {
		logger.Log.Errorf("[BaseDao] Update failed,error:%s",err.Error())
		return err
	}

	if p.UseCache(data) {
		err =  p.UpdateCache(data)
		if err != nil {
			logger.Log.Errorf("[BaseDao] Update failed,error:%s",err.Error())
			return err
		}
	}

	return nil
}

func (p *BaseDao) UpdateDb(data pojo.DbData,query string,args...interface{}) (int64,error) {
	r,err := p.enginer.Where(query,args...).Update(data)
	if err != nil {
		logger.Log.Errorf("Update failed,error:%s",err.Error())
		return 0,err
	}

	return r,nil
}

func (p *BaseDao) UpdateCache(data pojo.DbData) error {
	data.SetStatus(pojo.OptUpdate)
	bytes,err := gojson.Marshal(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] UpdateCache failed,error:%s",err.Error())
		return err
	}

	err = p.redisClient.HSet(data.TableName(),data.GetDataKey(),bytes).Err()
	if err != nil {
		logger.Log.Errorf("[BaseDao] UpdateCache failed,error:%s",err.Error())
		return err
	}


	return nil
}

func (p *BaseDao) Delete(data pojo.DbData) error {
	err := p.DeleteDb(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] Delete failed,error:%s",err.Error())
		return err
	}

	if p.UseCache(data){
		err = p.DeleteCache(data)
		if err != nil {
			logger.Log.Errorf("[BaseDao] Delete failed,error:%s",err.Error())
			return err
		}
	}

	return nil
}

func (p *BaseDao) DeleteDb(data pojo.DbData) error {
	_,err := p.enginer.Delete(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] DeleteDb failed,error:%s",err.Error())
		return err
	}
	return nil
}


func (p *BaseDao) DeleteCache(data pojo.DbData) error {
	if data != nil && len(data.GetDataKey()) > 0 {
		_,err := p.redisClient.HDel(data.TableName(),data.GetDataKey()).Result()
		if err != nil {
			logger.Log.Errorf("DeleteCache failed,error:%s",err.Error())
			return err
		}

	}

	return nil
}

func (p *BaseDao) Get(data pojo.DbData) (pojo.DbData,error) {
	var err error
	var rs pojo.DbData
	if p.UseCache(data) {
		rs,err = p.GetByCache(data)
		if err != nil {
			logger.Log.Errorf("[BaseDao] Get failed,error:%s",err.Error())
			return nil,err
		}

		if rs != nil {
			return rs,nil
		}
	}


	rs,err = p.GetByDb(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] Get failed,error:%s",err.Error())
		return nil,err
	}

	if p.UseCache(data) {
		err = p.AddCache(data)
		if err != nil {
			logger.Log.Errorf("[BaseDao] Get failed,error:%s",err.Error())
			return nil,err
		}

	}

	return rs,nil
}

func (p *BaseDao) GetByDb(data pojo.DbData) (pojo.DbData,error) {
	b,err := p.enginer.Get(data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] GetByDb failed,error:%s",err.Error())
		return nil,err
	}

	if !b {
		return nil,nil
	}

	return data,nil
}

func (p *BaseDao) GetByCache(data pojo.DbData) (pojo.DbData,error) {
	str,err := p.redisClient.HGet(data.TableName(),data.GetDataKey()).Result()
	if err != nil && err != redis.Nil {
		logger.Log.Errorf("[BaseDao] GetByCache failed,error:%s",err.Error())
		return nil,err
	}

	if str == "" {
		return nil,nil
	}


	err = gojson.Unmarshal([]byte(str),&data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] GetByCache failed,error:%s",err.Error())
		return nil,err
	}


	if data.GetStatus() == pojo.OptDelete {
		return nil,nil
	}

	return data,nil
}

func (p *BaseDao) GetListByDb(data pojo.DbData,rs interface{}) error {
	err := p.enginer.Find(rs,data)
	if err != nil {
		logger.Log.Errorf("[BaseDao] GetListByDb failed,error:%s",err.Error())
		return err
	}

	if p.UseCache(data) && rs != nil {
		length := reflect.ValueOf(rs).Elem().Len()
		if length > 0 {
			var bytes []byte
			for i := 0;i<length;i++ {
				v := reflect.ValueOf(rs).Elem().Index(i).Interface()

				bytes,err = gojson.Marshal(v)
				if err != nil {
					logger.Log.Errorf("[BaseDao] GetListByDb failed,error:%s",err.Error())
					return err
				}

				s := v.(pojo.DbData)
				err = p.redisClient.HSet(s.TableName(),s.GetDataKey(),bytes).Err()
				if err != nil && err != redis.Nil {
					logger.Log.Errorf("[BaseDao] GetListByDb failed,error:%s",err.Error())
					return err
				}
			}
		}
	}

	return nil
}

func (p *BaseDao) UseCache(data pojo.DbData) bool {
	return data.TableName() != "" && data.GetDataKey() != ""
}